﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Windows.Forms;
using SyringeControl.ControlSamples;
using System.Threading;
using System.Linq.Expressions;
using System.Collections;

namespace SyringeControl.WinForms.Tests
{
    [TestFixture]
    //[RequiresMTA]
    public class Test_BindingExtensions_Bind
    {
        static EventWaitHandle _handleInitialized = new AutoResetEvent(false);
        class SampleForm: Form
        {
            public TextBox tbString = new TextBox { Text = "PREVED"};
            public CheckBox cbFlag = new CheckBox { Enabled = false};
            public ListBox lbViewItemProperty = new ListBox { };
            public SampleForm(SampleModel model)
            {
                Controls.Add(tbString);
                Controls.Add(cbFlag);
                Controls.Add(lbViewItemProperty);
                Visible = false;
                model.Bind(tbString, m => m.StringField, tb => tb.Text);
                model.Bind(cbFlag, m => m.BoolField, cb => cb.Enabled);
                model.BindList(lbViewItemProperty, m => m.ListField)
                    .CurrentValue(m => m.SelectedDate)
                    .Display(dt => dt.Hour);
            }

            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
                tbString.Text = "hello";
                _handleInitialized.Set();
            }
            protected override void OnHandleCreated(EventArgs e)
            {
                base.OnHandleCreated(e);
                //_handleInitialized.Set();
            }
        }

        SampleForm form;
        SampleModel model;

        [SetUp]
        public void SetUp()
        {
            model = new SampleModel { StringField = "olleh"};
            form = new SampleForm(model);
            ThreadPool.QueueUserWorkItem(delegate
            {
                Application.Run(form);
            });
            if (!_handleInitialized.WaitOne(1000))
                throw new TimeoutException();
        }

        void Run(Action a)
        {
            form.Invoke(a);
        }

        [Test]
        public void Changes_in_model_are_seen_in_form()
        {
            Run(delegate {
                model.StringField = "hello";
                model.BoolField = true;
            });
            Assert.That(form.tbString.Text, Is.EqualTo(model.StringField));
            Assert.That(form.cbFlag.Enabled);
            form.Close();
        }

        [Test]
        public void Changes_in_form_are_seen_in_model()
        {
            Run(delegate {
                form.tbString.Text = "good bye";
                form.cbFlag.Enabled = true;
            });
            Assert.That(model.BoolField);
            Assert.That(model.StringField, Is.EqualTo(form.tbString.Text));
        }

        [Test]
        public void Changes_in_model_list_property_are_seen_in_view()
        {
            Run(delegate
            {
                model.ListField = new[] { DateTime.Today, DateTime.Today.AddHours(1), DateTime.Today.AddHours(2) };
            });
            Assert.That(form.lbViewItemProperty.Items.Count, Is.EqualTo(model.ListField.Count));
            Assert.That(form.lbViewItemProperty.Items, Is.EquivalentTo(model.ListField));
        }

        [Test]
        public void Selection_in_ListControl_are_seen_in_model_property()
        {
            var items = new[] { DateTime.Today, DateTime.Today.AddHours(1), DateTime.Today.AddHours(2) };
            Run(delegate
            {
                model.ListField = items;
                form.lbViewItemProperty.SelectedIndex = 2;
            });
            Assert.That(model.SelectedDate, Is.EqualTo(model.ListField[2]));
        }

        [Test]
        public void ListControl_shows_text_according_to_itemField_setting()
        {
            var items = new[] { DateTime.Today, DateTime.Today.AddHours(1), DateTime.Today.AddHours(2) };
            Run(delegate
            {
                model.ListField = items;
            });
            foreach (var item in form.lbViewItemProperty.Items)
            {
                var text = form.lbViewItemProperty.GetItemText(item);
                Assert.That(items.Select(it => it.Hour.ToString()).ToList(), Has.Member(text));
            }
        }

        //[Test]
        //public void Works_correctly_across_threads()
        //{
        //    model.BoolField = true;
        //    model.StringField = "hello";
        //    model.ListField = new[] { DateTime.Today, DateTime.Today.AddHours(1), DateTime.Today.AddHours(2) };
        //    Application.DoEvents();
        //    Assert.That(form.cbFlag.Enabled, Is.EqualTo(model.BoolField));
        //    Assert.That(form.tbString.Text, Is.EqualTo(model.StringField));
        //    Assert.That(form.lbViewItemProperty.Items, Is.EquivalentTo(model.ListField));
        //}

        [Test]
        public void List_property_item_change_can_be_seen_in_view()
        {
            Run(delegate
            {
                model.ListField = new[] { DateTime.Today, DateTime.Today.AddHours(1), DateTime.Today.AddHours(2) };
            });
            Assert.That(form.lbViewItemProperty.Items, Is.EquivalentTo(model.ListField));
            model.ListField[0] = DateTime.Today.AddDays(1);
            Assert.That(form.lbViewItemProperty.Items, Is.EquivalentTo(model.ListField));
        }

        [TestCase(true, true, true, true)]
        [TestCase(false, true, true, true, ExpectedException=typeof(ArgumentNullException))]
        [TestCase(true, false, true, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, true, false, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, true, true, false, ExpectedException = typeof(ArgumentNullException))]
        public void Validates_parameters_on_nullness(bool hasControl, bool hasModel, bool hasModelProp, bool hasCtrlProp)
        {
            var aControl = hasControl ? form : null;
            var aModel = hasModel ? model : null;
            var modelProp = hasModelProp ? (Expression<Func<SampleModel, string>>)(m => m.StringField) : null;
            var ctrlProp = hasCtrlProp ? (Expression<Func<Control, string>>)(m => m.Text) : null;
            aModel.Bind(aControl, modelProp, ctrlProp);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void Bind_checks_that_modelProperty_argument_really_describes_property()
        {
            model.Bind(form, m => m.GetType(), c => c.Text);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void Checks_that_controlProperty_argument_really_describes_property()
        {
            model.Bind(form, m => m.StringField, c => c.ToString());
        }

        [TestCase(false, true,true, true, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, false, true, true, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, true, false, true, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, true, true, false, true, ExpectedException = typeof(ArgumentNullException))]
        [TestCase(true, true, true, true, false, ExpectedException = typeof(ArgumentNullException))]
        public void BindList_checks_arguments_on_non_nullness(bool hasModel, bool hasControl, bool hasProp, bool hasDisplay, bool hasSelected)
        {
            var aModel = hasModel ? model : null;
            var aControl = hasControl ? form.lbViewItemProperty : null;
            var prop = hasProp ? (Expression<Func<SampleModel, IList<DateTime>>>)(m => m.ListField) :  null;
            var display = hasDisplay ? (Expression<Func<DateTime, int>>)(item => item.Day) : null;
            var selected = hasSelected ? (Expression<Func<SampleModel, DateTime>>)(m => m.SelectedDate) : null;
            aModel.BindList(aControl, prop).Display(display).CurrentValue(selected);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void BindList_checks_that_modelProperty_describes_property()
        {
            model.BindList(form.lbViewItemProperty, m => new List<DateTime>());
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void Display_checks_that_displayProperty_describes_property()
        {
            model.BindList(form.lbViewItemProperty, m => m.ListField).Display(dt => dt.ToString());
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void CurrentValue_checks_that_currentValueProperty_describes_property()
        {
            model.BindList(form.lbViewItemProperty, m => m.ListField).CurrentValue(m => DateTime.Now);
        }

        [TearDown]
        public void TearDown()
        {
            if(form != null)
                form.Close();
        }
    }
}
